#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>

#include "lock.h"

#define MAX_THREAD_CONTEXTS 8

/* Input parameters */
unsigned num_threads = 0;
unsigned shared_work = 0;
unsigned local_work = 0;
unsigned iterations = 0;

/* shared sum variable and synchronization objects 
 */
int padding1[8];
volatile unsigned long sum = 0;
int padding2[8];
mutex_t mutex;
int padding3[8];
barrier_t barrier;
int padding4[8];


/* executed by every thread */
void 
*thread(void *thread_info){
  int i,j, result;
  int tid = (int) thread_info;
  unsigned long local_sum = 0;

  // bind threads to processors
  int mask, even, odd;
  mask = 1 << (tid % MAX_THREAD_CONTEXTS); 
  if ((result = sched_setaffinity(0, sizeof(mask), (cpu_set_t *) &mask))){
    printf("ERROR; return code from sched_setaffinity() is: %d\n", result);
    exit(-1);
  }

  // wait for all other threads before beginning
  barrier_wait(&barrier);

  /* do a large number of iterations for measurement purposes this is divided
   * by the number of threads to ensure a constant amount of total work when
   * varying the number of threads 
   */
  for (i = 0; i < iterations/num_threads; i++){

    // do shared work within a critical section
    mutex_lock(&mutex);
    for (j=0;j<shared_work;j++){
      sum += j*j;
    }
    local_sum = sum;
    mutex_unlock(&mutex);

    // do local work (not a critical section)
    for (j=0;j<local_work;j++){
      local_sum += j*j;
    }
  }

  // wait for all other threads before finishing
  barrier_wait(&barrier);
  printf("local sum thread %d is %u\n",tid,local_sum);
}



int 
main(int argc, char* argv[]){
  int i, result;
  pthread_t *threads;
  
  // for measuring the execution time
  struct timeval tv;
  struct timezone tz;
  double start_time, end_time;

  if (argc != 5){
    printf("Usage: %s <num_threads> <iterations> <shared_work> <local_work>\n", argv[0]);
    exit(1);  
  }

  sscanf(argv[1], " %d", &num_threads);
  if (num_threads < 1) {
    printf("Bad number of processors (%d)\n", num_threads);
    exit(1);  
  }
  sscanf(argv[2], " %d", &iterations);
  sscanf(argv[3], " %d", &shared_work);
  sscanf(argv[4], " %d", &local_work);

  threads = (pthread_t *) malloc(sizeof(pthread_t) * num_threads);

  // initialize synchronization objects
  mutex_init(&mutex);
  barrier_init(&barrier,num_threads);

  // create threads
  for (i = 1; i < num_threads; i++){
    if (result = pthread_create(&threads[i], NULL, thread, (void *) i)){
      printf("ERROR; return code from pthread_create() is: %d\n", result);
      exit(-1);
    }
  }

  gettimeofday (&tv, &tz);
  start_time = (tv.tv_sec - 1134000000) + (double)((double) tv.tv_usec / 1000000);

  // the main thread does work too
  thread((void *) 0);

  gettimeofday (&tv, &tz);
  end_time = (tv.tv_sec - 1134000000) + (double)((double) tv.tv_usec / 1000000);

  // wait for all threads to exit.
  for (i = 1; i < num_threads; i++)
    pthread_join(threads[i], NULL);

  printf("sum is: %lu\n", sum);
  printf("time: %18.12g\n", end_time - start_time);
}
